fix(runtime): name ripgrep when Grep cannot run it - #5169
Conversation
Grep failed with a bare `spawn rg ENOENT` on the local path and a generic "Grep is unavailable in this runtime." in the filesystem worker. Both now fail with grep_unavailable and copy that names ripgrep, an install command and the next step (retry locally, restart Maka for the worker). The local path checks that the cwd still exists before blaming ripgrep, because Node reports a missing spawn cwd with the same ENOENT. The worker maps a spawn ENOENT for its startup-resolved executable to grep_unavailable instead of not_found, which read as a missing search path. Fixes apache#5167 Generated-by: Claude Code Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The shared ripgrep copy is imported by the filesystem worker and the builtin tools, so it joins the Windows package closure and the recovery filter. Register the new POSIX-only EACCES test in the Windows skip inventory as a platform contract. Generated-by: Claude Code Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
PR 5169 Review
结论
APPROVE
两条 Grep 失败路径现在都报 grep_unavailable 并点名 ripgrep。worker 路径(operations.ts:426-433)只在 ENOENT 时映射为 stale-executable 消息,EACCES/其他错误仍走原 filesystem_error 归一(operations.ts:491-497),与描述一致;grep_unavailable 本就在 worker protocol 错误码集中(protocol.ts:230),local executor 用 RipgrepUnavailableError.code='grep_unavailable' 作对应 twin。local 路径用 isDirectory(input.cwd) 区分"rg 缺失"与"cwd 被删",ENOENT 在 cwd 不存在时保持原错误。Windows 沙箱拒绝分支在 grepExecutable 检查之前,未被触碰。
There was a problem hiding this comment.
Maka auto review
[P2] Complete the missing-ripgrep recovery path: install in the execution environment, then retry.
This PR improves the diagnosis: users learn that Grep depends on ripgrep instead of seeing a generic filesystem failure. The remaining concern is that the suggested recovery, “Then restart Maka,” does not identify which process or environment must change, and restarting Desktop does not always refresh the worker's dependency discovery.
Concrete user scenario
- Desktop is connected to a separately managed Host, such as one running in WSL or on an SSH server.
- The filesystem worker launch configuration is first resolved without a usable rg.
- Grep returns the new installation/restart message.
- The user installs ripgrep in the execution environment and closes/reopens Desktop.
- The independently running Host remains alive, and its cached worker launch configuration still has no rg. A retry continues to fail even though the dependency is now available.
Installing on the Desktop machine instead of the execution machine is another understandable mistake when the message does not name the environment. Similarly, a package upgrade can remove a previously resolved executable path while a long-lived Host keeps using that path.
Why this happens
packages/runtime/src/filesystem-worker/launch-spec.ts:createFilesystemWorkerLaunchSpecProvidermemoizesresolveLaunchSpec(input)incached. Resolution is lazy on the first request, rather than necessarily occurring at process startup. The cached result includes dependency discovery, so a successful launch-spec resolution can still contain no usable rg.packages/runtime-host/src/server/execution-composition.ts: the Host creates and retains that provider for its filesystem worker client. Relaunching an individual worker or reconnecting Desktop does not itself create a new provider.apps/desktop/src/main/runtime-host-desktop-manager.ts:prepareOwnedLocalHostQuitonly retires anowned_ephemerallocal Host. A separately managed or remote Host is not stopped by this Desktop quit path.- Closing a window is also not a reliable process restart: macOS and Windows tray behavior can keep Desktop running.
The existing local executor looks up rg on PATH for each call, so its “install, then retry” guidance is already consistent with its behavior. The requested change concerns the worker's cached configuration and the recovery guidance for it. This caching predates the PR; the concern here is completing the recovery path now presented to users, not claiming the PR introduced the cache.
Requested recovery behavior
Prefer install ripgrep, then retry without restarting the entire service merely to refresh dependency discovery:
- Identify the task's execution environment in user-facing guidance, for example “this machine,” “WSL · Ubuntu,” or “SSH · dev-server.” Users should not need to understand Host ownership to know where to install rg.
- When dependency discovery found no usable rg, or the pinned executable has disappeared, let a subsequent attempt refresh discovery and rebuild the relevant worker launch configuration.
- Re-run executable inspection and sandbox dependency-root validation for the replacement. Do not just substitute a new binary path into the old sandbox configuration.
- Preserve useful caching on the healthy path, bound refresh/retry work, and keep Windows sandbox capability refusal distinct from a missing dependency.
- Installing into a new PATH directory may also require refreshing the environment used for discovery. Do not promise recovery from an unchanged inherited PATH that cannot see the installation. If automatic recovery is unavailable, provide an accurate, environment-specific next step.
This does not require automatically installing packages. It asks that installation followed by retry have an effective recovery path, avoiding an unnecessary service restart that can affect other tasks.
Acceptance checks
- With the same Host and launch-spec provider alive: resolve while rg is absent, install rg into a discoverable location, retry, and verify actual search results.
- Resolve an rg path, remove/replace it, then retry and verify the replacement is discovered and its required sandbox paths are validated.
- Repeated retries while rg remains absent terminate normally with actionable guidance, rather than looping.
- Preserve distinctions for missing search paths, permission failures, no matches, and unsupported Windows sandbox execution.
Validation so far: the reviewed head built successfully; 72 relevant Linux tests passed, including real subprocess cases for missing rg, a vanished executable, a missing working directory, and an executable permission failure. Those tests validate error classification; they do not establish the two same-Host recovery transitions above. The Desktop/Host lifecycle analysis is source-based, not a claim that a full SSH/WSL installation-and-restart scenario was exercised.
中文
[P2] 补齐恢复路径:在实际执行环境安装 ripgrep,然后重试。
本 PR 明确指出 Grep 依赖 ripgrep,这一点有价值。剩余问题是“重启 Maka”没有说明应该改变哪个环境或进程,重启 Desktop 也不一定刷新 worker 的依赖检测。
用户会遇到什么
- Desktop 连接一个独立管理的执行服务,例如 WSL 或 SSH 服务器上的服务。
- 首次解析文件系统 worker 启动配置时,没有找到可用的 rg。
- Grep 提示安装并重启 Maka。
- 用户在执行环境安装后,关闭并重新打开 Desktop。
- 原来的执行服务仍在运行,缓存的启动配置仍然没有 rg,重试继续失败。
提示不说明位置时,用户还可能把 rg 安装在 Desktop 所在机器,而实际任务在 WSL/SSH 环境执行。另一种情况是包升级移除了旧 rg 路径,但长时间运行的服务仍持有旧路径。
代码依据
createFilesystemWorkerLaunchSpecProvider缓存第一次resolveLaunchSpec(input)的结果。严格说,这是首次请求时的惰性解析,不一定发生在进程启动瞬间;启动配置解析成功也可能没有找到 rg。- Host 的执行组合保留该 provider。因此,重新启动单次 worker 或重连 Desktop 本身不会刷新它。
- Desktop 的
prepareOwnedLocalHostQuit只处理自己拥有的临时本地 Host。独立管理或远程服务不会因此退出。 - 仅关闭窗口更不等于退出应用,macOS 和 Windows 托盘场景都可能继续后台运行。
普通本地 executor 每次调用都会从 PATH 查找 rg,因此其“安装后重试”提示与行为一致。这里关注 worker 的缓存和对应恢复流程。缓存机制在本 PR 之前就存在,并非本 PR 新引入;本次意见是要求补齐现在向用户提供的恢复路径。
建议的行为
优先支持 安装后重试,避免仅为刷新依赖检测而重启整个服务、影响其他任务:
- 告诉用户安装位置,例如“本机”“WSL · Ubuntu”“SSH · dev-server”,无需让用户理解 Host 归属。
- 首次未找到 rg,或固定的执行路径消失后,后续尝试可以重新检测并重建相关启动配置。
- 新路径必须重新执行可执行文件检查和沙箱依赖路径验证,不能只替换二进制路径而继续使用旧沙箱配置。
- 正常路径可以保留缓存;刷新和重试必须有界;Windows 沙箱能力限制仍应单独解释。
- 如果安装改变了 PATH,检测所使用的环境也需要考虑刷新。不能在仍然看不到新安装位置的旧 PATH 上承诺重试必然成功;无法自动恢复时,应给出针对具体环境的有效操作。
不要求自动安装软件包;要求安装后的重试能够真正恢复。
验收建议
- 保持同一个 Host 和 provider:首次缺少 rg,随后安装到可发现位置,重试能够返回真实搜索结果。
- 原 rg 路径被删除/替换后,重试找到新路径,并验证其沙箱依赖路径。
- 始终未安装时,多次重试都能正常结束并给出提示,不进入无限重试。
- 目录不存在、权限不足、没有匹配以及 Windows 沙箱不支持的情况,保持正确区分。
本轮已完成构建和 72 项 Linux 测试,包括实际子进程的缺少 rg、执行路径消失、工作目录缺失和执行权限不足场景。这些验证了错误分类,并未验证上述同一个 Host 内安装/替换后的恢复过程。Desktop 与 Host 生命周期结论来自源码调用链,未声称已完整实测 SSH/WSL 安装重启场景。
| ): string { | ||
| return `Grep requires ripgrep (\`rg\`), but no usable copy was found when Maka started. ${ripgrepInstallHint(platform)} Then restart Maka.`; | ||
| } | ||
|
|
There was a problem hiding this comment.
[P2] Make retry refresh the worker's ripgrep configuration
"Then restart Maka" does not reliably recover this failure for Desktop users. createFilesystemWorkerLaunchSpecProvider caches its first resolution, including an absent ripgrep or a pinned executable path. Installing ripgrep after that resolution does not refresh the cached worker configuration. Desktop quit only retires its owned ephemeral local Host; a separately managed or remote/WSL Host can remain running when the user restarts Desktop.
Please prefer an install, then retry recovery path: when ripgrep is unavailable or its resolved executable has disappeared, allow a subsequent attempt to re-resolve it in the task's execution environment and rebuild the relevant worker launch configuration. Preserve executable inspection and sandbox dependency-root validation when refreshing it. Keep successful configuration caching if appropriate, and avoid restarting the entire service merely to refresh dependency discovery.
The guidance should identify the execution environment (local machine, WSL distribution, or SSH server) so users know where to install the dependency without needing to understand Host lifecycle ownership.
Please cover both transitions: initially missing rg → installed → retry succeeds, and pinned rg removed/replaced → retry resolves the replacement. They should recover within the same running Host without broadening sandbox access.
中文
“重启 Maka”在 Desktop 场景下不一定能恢复:worker 的启动配置会缓存首次检测结果,包括未找到 rg 或已经固定的 rg 路径。安装后重试仍会使用该缓存。Desktop 退出只负责停止自己拥有的临时本地 Host;独立服务或远程/WSL Host 可能在重启 Desktop 后继续运行。
建议优先支持 安装后重试:rg 不可用或原执行路径消失时,让后续尝试在任务实际执行的环境重新检测 rg,并重建相关 worker 启动配置。刷新时仍需执行可执行文件检查和沙箱依赖路径验证,可以保留正常情况下的配置缓存。避免仅为刷新依赖检测而重启整个服务、影响其他任务。
提示应指出安装位置,例如本机、具体 WSL 发行版或 SSH 服务器,让用户无需理解 Host 生命周期。
建议覆盖两条恢复测试:首次缺少 rg → 安装 → 重试成功;固定的 rg 被删除或替换 → 重试找到新路径。两者都应在同一个运行中的 Host 内恢复,并保持沙箱边界。
There was a problem hiding this comment.
Thanks, agreed. Restart was the wrong recovery, especially for a Host that outlives Desktop. Addressed in ebc6df0:
- The launch configuration keeps a resolved ripgrep while the same file is still there, and looks it up again on the next launch when it was missing, has disappeared, or was replaced in place, with the same executable check and macOS dependency-root inspection. Installing ripgrep and retrying now recovers in the same running Host, and the sandbox only grants the copy that was found. Inspection results are kept per file identity, so an unchanged binary that can't be granted doesn't run
otoolon every launch. - The guidance says where to install it: the WSL distribution when
WSL_DISTRO_NAMEis set, otherwise the machine the Host runs on, noting that for a remote Host that is the server rather than this computer. I left the hostname out on purpose: this text goes to the model as tool output, a default macOS hostname carries its owner's name, and Maka otherwise only uses it as a device name in Desktop, which already shows the active Host. - On Windows the winget Links directory is also a candidate, because a running Host keeps the PATH it started with.
- Tests cover both transitions you asked for (missing → installed → the next launch finds it; a pinned copy removed with a replacement elsewhere), plus a copy reinstalled in place. Each fails if its re-resolution is removed.
One thing is unchanged: the local executor still looks rg up on the Host's own PATH, so an install into a directory that PATH doesn't include is only picked up once the Host sees the new PATH.
Could you take another look when you have a moment?
Changes made with Claude Code; I reviewed and ran them.
me2seeks
left a comment
There was a problem hiding this comment.
Maka auto review
Requesting changes; this decision supersedes my earlier approval. Before merging, please address the effective recovery path after ripgrep is installed in the task's execution environment, preferably by re-detecting rg on retry and refreshing the relevant worker configuration while preserving sandbox validation.
The concrete scenarios, source evidence, and acceptance checks are in the detailed review and its existing discussion. The cache predates this PR; this request concerns completing the recovery path presented to users, rather than a newly introduced caching regression.
中文
本次 Request changes 取代我此前的 Approve。合并前请处理在任务实际执行环境安装 ripgrep 后的有效恢复路径,优先支持重试时重新检测 rg、刷新相关 worker 配置,并保留沙箱验证。
具体场景、源码依据和验收要求见上方链接的详细 review 及已有讨论。缓存机制在此 PR 之前已存在;本次要求是补齐向用户提供的恢复流程,并非声称此 PR 引入了缓存回归。
The filesystem worker's launch configuration cached its first ripgrep lookup for the life of the Host, so installing ripgrep, or an upgrade that moved it, needed a Host restart that quitting Desktop does not always perform. Keep a resolved copy while the same file is still there, and look again on the next launch when it was missing, has disappeared or was replaced in place, with the same executable and dependency-root checks, so the sandbox only grants the copy found. Mach-O inspection results are kept per file identity, so an unchanged binary never runs otool twice. The guidance now says where to install ripgrep and to retry. It names the WSL distribution when there is one and otherwise the machine the Host runs on, without the hostname: the text reaches the model as tool output. On Windows the winget Links directory is also a candidate, since a running Host keeps the PATH it started with. Refs apache#5167 Generated-by: Claude Code Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Without ripgrep, every Grep call on the local path failed with a bare
spawn rg ENOENT, and the filesystem worker said only "Grep is unavailable in this runtime." Both paths now fail withgrep_unavailableand a message that names ripgrep, gives a platform install command and the official install page. Both say where to install it and to retry: the local path looksrgup on every call, and the worker's launch configuration now looks ripgrep up again whenever the copy it knew is missing, gone or replaced, so installing it recovers without restarting the Host. The location is the WSL distribution when there is one, and otherwise the machine the Host runs on, noting that for a remote Host that is the server. The hostname is deliberately left out, because this text reaches the model as tool output.Three cases needed more than new copy:
Node reports a missing spawn
cwdexactly like a missing executable (spawn rg ENOENTboth ways). The local path only blames ripgrep after confirming the cwd still exists; a deleted session directory keeps its original error.The worker pins ripgrep's realpath at startup. If that file disappears later, for example when
brew upgraderemoves the old keg, the spawnENOENTwas normalized tonot_found("The requested path was not found.", marked recoverable), which reads as a missing search path. The worker runsrgfrom the filesystem root, so thatENOENTnow maps togrep_unavailablewith the stale path. The launch configuration checks the file before every launch, so this is now only the window between that check and the spawn.The worker's launch configuration cached its first ripgrep lookup for the life of the Host, including "not found", and quitting Desktop does not stop a Host it does not own (a separately managed, WSL or SSH Host). It now keeps a resolved copy while the same file is still there and looks again on the next launch when it was missing, has disappeared or was replaced in place, with the same executable check and macOS dependency-root inspection, so the sandbox only grants the copy it found. Inspection results are kept per file identity, so an unchanged binary never runs
otooltwice. On Windows the winget Links directory is also a candidate, since a running Host keeps the PATH it started with.Exit code 1 still means no matches, and
EACCES, timeouts and other spawn errors keep their current handling. The Windows sandbox refusal is unchanged. Shipping ripgrep with the product (Phase 2 in the issue) is out of scope.Fixes #5167
Thanks to @colaforniaw for the analysis and the phased plan in the issue.
Verification
Local macOS arm64, Node 22.23.1, dependencies installed with npm 11.19.0:
test:dist): 3,423 tests, 3,410 passed, 13 skipped, 0 failed.workspace-executor.test.tsandfilesystem-worker.test.ts. Onmainthe three conversion tests fail (ENOENT,not_found, and the old copy). The two guards, missing cwd keepsENOENTand a non-executablergkeepsEACCES, pass on both; removing the cwd check makes the missing-cwd guard fail.npm run typecheck(all workspaces), Biome lint and format, the Windows package-closure and test-inventory checks, the ASF header check andgit diff --checkpass on the latest commit; the fullnpm run buildand knip runs were on the earlier commits.Local path with no
rgonPATH:Not run locally: Linux, Windows, and the suites of the workspaces downstream of runtime. CI covers them for this diff:
scripts/ci-test-plan.mjsselects the runtime, eval, computer-use, cli, desktop and runtime-host suites plus the Linux sandbox smoke, and thefilesystem-worker/**path filter triggers the Windows sandbox workflow. The packaged desktop app, and recovery on a real WSL or SSH Host, were not exercised.AI use
Tool(s) and scope: Claude Code investigated the defect, authored the implementation and regression tests, ran local verification, and prepared the contribution drafts. Submitted with contributor approval.
Checklist
Does this PR entail a change in behavior?
🤖 Generated with Claude Code